﻿using System;
using System.Linq;
using COFE.Core;
using COFE.Core.Filter;
using COFE.Core.Profile;
using COFE.Core.Utils;
using System.Linq.Expressions;
using System.Collections.Generic;
using COFE.Data.Filter;

namespace COFE.Data.Utils
{
    public static class DBFilterUtils
    {
        #region Filter2DB
        public static COFE.Data.EntryFilter[] Filter2DB(COFEDB2 context, IEntryFilter[] filterArray)
        {
            return (from f in filterArray select Filter2DB(context, f)).ToArray();
        }

        public static COFE.Data.OptionFilter[] Filter2DB(COFEDB2 context, IOptionFilter[] filterArray)
        {
            return (from f in filterArray select Filter2DB(context, f)).ToArray();
        }

        /// <summary>
        /// Create and add a DBEntryFilter to context.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="filter"></param>
        /// <returns></returns>
        public static COFE.Data.EntryFilter Filter2DB(COFEDB2 context, IEntryFilter filter)
        {
            var filterGuid = filter.GetId();
            var dbFilter = context.EntryFilters.FirstOrDefault(ef => ef.EntryFilterId == filterGuid);
            if (dbFilter != null)
            {
                return dbFilter;
            }

            if (filter is COFE.Core.Filter.IMultiEntryFilter)
            {
                var mf = filter as COFE.Core.Filter.IMultiEntryFilter;

                MultiEntryFilter mdbFilter;
                dbFilter = mdbFilter = COFE.Data.MultiEntryFilter.CreateMultiEntryFilter(filterGuid, mf.Type);

                int position = 0;
                foreach (var subfilter in mf.EntryFilters)
                {
                    var dbSub = Filter2DB(context, subfilter);
                    var mfLink = MultiEntryFilter_Child.CreateMultiEntryFilter_Child(filterGuid, dbSub.EntryFilterId, position++);
                    mdbFilter.EntryFilters.Add(mfLink);
                }
            }
            else
                if (filter is COFE.Core.Filter.RootEntryFilter)
                {
                    var rootFilter = filter as COFE.Core.Filter.RootEntryFilter;

                    dbFilter = COFE.Data.RootEntryFilter
                        .CreateRootEntryFilter(filterGuid,
                        rootFilter.Negative, rootFilter.Directory.GetId());
                }
                else
                {
                    dbFilter = COFE.Data.KeyValueEntryFilter
                        .CreateKeyValueEntryFilter(filterGuid,
                        filter.Type, filter.Value);

                }

            context.EntryFilters.AddObject(dbFilter);
            return dbFilter;
        }

        public static COFE.Data.OptionFilter Filter2DB(COFEDB2 context, IOptionFilter filter)
        {
            var filterGuid = filter.GetId();
            var dbFilter = context.OptionFilters.FirstOrDefault(lf => lf.OptionFilterId == filterGuid);
            if (dbFilter != null)
                return dbFilter;
            else
            {
                dbFilter = COFE.Data.KeyValueOptionFilter.
                    CreateKeyValueOptionFilter(filterGuid, filter.Type, filter.Value);
            }

            context.OptionFilters.AddObject(dbFilter);
            return dbFilter;
        }
        #endregion

        #region DB2Filter
        public static IEntryFilter[] DB2Filter(COFEDB2 context, COFE.Data.EntryFilter[] filterArray)
        {
            return (from f in filterArray select DB2Filter(context, f)).ToArray();
        }

        public static IOptionFilter[] DB2Filter(COFEDB2 context, COFE.Data.OptionFilter[] filterArray)
        {
            return (from f in filterArray select DB2Filter(context, f)).ToArray();
        }

        public static IEntryFilter DB2Filter(COFEDB2 context, COFE.Data.EntryFilter filter)
        {
            if (filter is COFE.Data.KeyValueEntryFilter)
            {
                var kvef = filter as COFE.Data.KeyValueEntryFilter;
                return FilterUtils2<IEntryFilter>.StringToFilter(kvef.Key, kvef.Value);
            }
            else
                if (filter is COFE.Data.RootEntryFilter)
                {
                    var rlf = filter as COFE.Data.RootEntryFilter;
                    rlf.RootEntryReference.Load();
                    var id = rlf.Negative ? "-root" : "root";

                    return new COFE.Core.Filter.RootEntryFilter(
                        DBEntryConstructor.CreateEntry(rlf.RootEntry,
                        EntryConstructionFlags.Default) as IDirectoryInfo, id);
                }
                else
                    if (filter is COFE.Data.MultiEntryFilter)
                    {
                        var mef = filter as COFE.Data.MultiEntryFilter;
                        mef.EntryFilters.Load();
                        var childFilters =
                            mef.EntryFilters.Select(f =>
                                context.EntryFilters.First(ef => ef.EntryFilterId == f.ChildEntryFilterId)
                                ).ToArray();
                        switch (mef.Type.ToLower())
                        {
                            case "or":
                                return new OrEntryFilter(DB2Filter(context, childFilters));
                            //case "and":
                            //    return new AndEntryFilter(DB2Filter(context, childFilters));
                            default: throw new ArgumentException("Unrecognize type : " + mef.Type);

                        }
                    }

            if (filter == null)
                throw new ArgumentException("Filter cannot be null.");
            throw new NotSupportedException("Unsupported entry filter type : " + filter.GetType().ToString());
        }


        public static IOptionFilter DB2Filter(COFEDB2 context, COFE.Data.OptionFilter filter)
        {
            if (filter is COFE.Data.KeyValueOptionFilter)
            {
                var kvlf = filter as COFE.Data.KeyValueOptionFilter;
                return FilterUtils2<IOptionFilter>.StringToFilter(kvlf.Key, kvlf.Value);
            }

            throw new NotSupportedException("Unsupported lookup filter type : " + filter.GetType().ToString());
        }
        #endregion


        private static Dictionary<Type, IFilterExpression<IEntryFilter>>
            entryFilterExpressions = new Dictionary<Type, IFilterExpression<IEntryFilter>>();
        private static Dictionary<Type, IFilterExpression<IOptionFilter>>
            optionFilterExpressions = new Dictionary<Type, IFilterExpression<IOptionFilter>>();


        public static void RegisterFilterExpression(IFilterExpression<IEntryFilter> filterExpression)
        {
            if (!entryFilterExpressions.ContainsKey(filterExpression.SupportedType))
                entryFilterExpressions.Add(filterExpression.SupportedType, filterExpression);
            else entryFilterExpressions[filterExpression.SupportedType] = filterExpression;
        }

        public static void RegisterFilterExpression(IFilterExpression<IOptionFilter> filterExpression)
        {
            if (!optionFilterExpressions.ContainsKey(filterExpression.SupportedType))
                optionFilterExpressions.Add(filterExpression.SupportedType, filterExpression);
            else optionFilterExpressions[filterExpression.SupportedType] = filterExpression;
        }

        public static IFilterExpression<IEntryFilter> GetFilterExpression(IEntryFilter filter)
        {
            var filterType = filter.GetType();
            if (entryFilterExpressions.ContainsKey(filterType))
                return entryFilterExpressions[filterType];
            return null;
        }

        public static IFilterExpression<IOptionFilter> GetFilterExpression(IOptionFilter filter)
        {
            var filterType = filter.GetType();
            if (optionFilterExpressions.ContainsKey(filterType))
                return optionFilterExpressions[filterType];
            return null;
        }

        public static IQueryable<Entry>
            ProcessQuery(IQueryable<Entry> input, IFilter filter, IOptionFilter[] options)
        {
            var filterType = filter.GetType();

            if (filter is IEntryFilter)
            {
                var entryFilter = filter as IEntryFilter;
                if (entryFilterExpressions.ContainsKey(filterType))
                {
                    var filterExpression = entryFilterExpressions[filterType];
                    return filterExpression.ProcessQuery(input, entryFilter, options);
                }
            }

            if (filter is IOptionFilter)
            {
                var optionFilter = filter as IOptionFilter;
                if (optionFilterExpressions.ContainsKey(filterType))
                {
                    var filterExpression = optionFilterExpressions[filterType];
                    return filterExpression.ProcessQuery(input, optionFilter, options);
                }
            }
            return input;
        }

        public static IEnumerable<Entry>
            ProcessQueryOutput(IEnumerable<Entry> input, IFilter filter, IOptionFilter[] options)
        {
            var filterType = filter.GetType();

            if (filter is IEntryFilter)
            {
                var entryFilter = filter as IEntryFilter;
                if (entryFilterExpressions.ContainsKey(filterType))
                {
                    var filterExpression = entryFilterExpressions[filterType];
                    return filterExpression.ProcessQueryOutput(input, entryFilter, options);
                }
            }

            if (filter is IOptionFilter)
            {
                var optionFilter = filter as IOptionFilter;
                if (optionFilterExpressions.ContainsKey(filterType))
                {
                    var filterExpression = optionFilterExpressions[filterType];
                    return filterExpression.ProcessQueryOutput(input, optionFilter, options);
                }
            }
            return input;
        }

        public static Expression<Func<Entry, bool>>
            GetExpression(IEntryFilter entryFilter, IOptionFilter[] options)
        {
            var filterType = entryFilter.GetType();

            if (entryFilter is IEntryFilter)
            {
                if (entryFilterExpressions.ContainsKey(filterType))
                {
                    var filterExpression = entryFilterExpressions[filterType]
                        as IEntryFilterExpression<IEntryFilter>;
                    return filterExpression.GetQueryExpression(entryFilter, options);
                }
            }

            return (e) => true;
        }

        //public static Expression<Func<Entry, bool>>
        //    GetFilterExpression(IEntryFilter[] entryFilters, IOptionFilter[] options)
        //{
        //    var parameterExpression = Expression.Parameter(typeof(Entry));
        //    Expression<Func<Entry, bool>> readOutput = null;

        //    foreach (var ef in entryFilters)
        //    {
        //        var exp = GetFilterExpression(ef, options);

        //        readOutput = readOutput.  && exp;
        //            //readOutput == null ? ()exp 
        //            //: Expression.AndAlso(readOutput, exp);

        //        //readOutput = Expression.Lambda<Func<Entry, bool>>(
        //        //    Expression.And(
        //        //    Expression.Invoke(readOutput, parameterExpression),
        //        //    Expression.Invoke(exp, parameterExpression)
        //        //    , ));                    

        //        //readOutput = Expression<Func<Entry, bool>>.AndAlso(readOutput, exp);
        //    }

        //    return (Expression.Lambda<Func<Table, bool>>)readOutput;
        //}
    }
}
